/*
 *  Copyright 2006 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.ibatis.abator.internal.java.model;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.abator.api.CommentGenerator;
import org.apache.ibatis.abator.api.FullyQualifiedTable;
import org.apache.ibatis.abator.api.GeneratedJavaFile;
import org.apache.ibatis.abator.api.IntrospectedTable;
import org.apache.ibatis.abator.api.JavaModelGenerator;
import org.apache.ibatis.abator.api.ProgressCallback;
import org.apache.ibatis.abator.api.dom.OutputUtilities;
import org.apache.ibatis.abator.api.dom.java.Field;
import org.apache.ibatis.abator.api.dom.java.FullyQualifiedJavaType;
import org.apache.ibatis.abator.api.dom.java.InnerClass;
import org.apache.ibatis.abator.api.dom.java.JavaVisibility;
import org.apache.ibatis.abator.api.dom.java.Method;
import org.apache.ibatis.abator.api.dom.java.Parameter;
import org.apache.ibatis.abator.api.dom.java.TopLevelClass;
import org.apache.ibatis.abator.config.AbatorContext;
import org.apache.ibatis.abator.config.PropertyRegistry;
import org.apache.ibatis.abator.internal.db.ColumnDefinition;
import org.apache.ibatis.abator.internal.rules.AbatorRules;
import org.apache.ibatis.abator.internal.util.ClassloaderUtility;
import org.apache.ibatis.abator.internal.util.JavaBeansUtil;
import org.apache.ibatis.abator.internal.util.StringUtility;
import org.apache.ibatis.abator.internal.util.messages.Messages;

/**
 * This class supports the following properties:
 * 
 * <dl>
 * <dt>trimStrings
 * <dt>
 * <dd>If true, the setters will trim all Strings. Default is false.</dd>
 * 
 * <dt>enableSubPackages
 * <dt>
 * <dd>If true, the classes will be generated in sub-packaged based on the
 * database catalg and schema - else the will be generated in the specified
 * package (the targetPackage attribute). Default is false.</dd>
 * 
 * <dt>rootClass
 * <dt>
 * <dd>If specified, then the root class of all objects generated by the
 * generator will be used as specified. No checking is done to see if the
 * specified class exists, or if the generated classes hide any attributes or
 * methods in the specified class. Note that the root class is not the base
 * class of all objects - just the root class. For example, if there is a
 * primary key then the primary key will extend the root class and the record
 * class will still extend the primary key.</dd>
 * </dl>
 * 
 * @author Jeff Butler
 */
public class JavaModelGeneratorJava2Impl implements JavaModelGenerator {

	protected AbatorContext abatorContext;
	protected List warnings;

	/**
	 * The properties from the JavaModelGenerator congiguration element
	 */
	protected Properties properties;

	/**
	 * The target package from the JavaModelGenerator congiguration element
	 */
	protected String targetPackage;

	/**
	 * The target project from the JavaModelGenerator congiguration element
	 */
	protected String targetProject;

	private Map tableValueMaps;

	private Map propertyDescriptorMap;

	private ClassLoader rootClassLoader;

	public JavaModelGeneratorJava2Impl() {
		super();
		tableValueMaps = new HashMap();
		properties = new Properties();
		propertyDescriptorMap = new HashMap();
	}

	public void addConfigurationProperties(Properties properties) {
		this.properties.putAll(properties);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.ibatis.abator.api.JavaModelGenerator#setTargetPackage(java
	 * .lang.String)
	 */
	public void setTargetPackage(String targetPackage) {
		this.targetPackage = targetPackage;
	}

	private Map getTableValueMap(FullyQualifiedTable table) {
		Map map = (Map) tableValueMaps.get(table);
		if (map == null) {
			map = new HashMap();
			tableValueMaps.put(table, map);
		}

		return map;
	}

	/**
	 * Adds fields and getter/setter methods for each ColumnDefinition passed
	 * into the method.
	 * 
	 * @param table
	 *            the table from which the ColumnDefinitions are derived. This
	 *            is used to generate appropriate JavaDoc comments for the
	 *            generated fields and methods.
	 * @param columnDefinitions
	 *            the collection of ColumnDefinitions used to generate fields
	 *            and getter/setter methods.
	 * @param topLevelClass
	 *            the generated fields and methods will be added to this object
	 */
	protected void generateClassParts(FullyQualifiedTable table, Iterator columnDefinitions, TopLevelClass topLevelClass, String rootClass) {

		boolean trimStrings = "true".equalsIgnoreCase(properties //$NON-NLS-1$
				.getProperty(PropertyRegistry.MODEL_GENERATOR_TRIM_STRINGS));

		StringBuffer sb = new StringBuffer();
		Field field;
		Method method;

		CommentGenerator commentGenerator = abatorContext.getCommentGenerator();

		while (columnDefinitions.hasNext()) {
			ColumnDefinition cd = (ColumnDefinition) columnDefinitions.next();
			if (propertyExistsInRootClass(cd, rootClass)) {
				continue;
			}

			FullyQualifiedJavaType fqjt = cd.getResolvedJavaType().getFullyQualifiedJavaType();

			topLevelClass.addImportedType(fqjt);

			String property = cd.getJavaProperty();

			field = new Field();
			field.setVisibility(JavaVisibility.PRIVATE);
			field.setType(fqjt);
			field.setName(property);
			commentGenerator.addFieldComment(field, table, cd.getActualColumnName());
			topLevelClass.addField(field);

			method = new Method();
			method.setVisibility(JavaVisibility.PUBLIC);
			method.setReturnType(fqjt);
			method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
			commentGenerator.addGetterComment(method, table, cd.getActualColumnName());
			sb.setLength(0);
			sb.append("return "); //$NON-NLS-1$
			sb.append(property);
			sb.append(';');
			method.addBodyLine(sb.toString());
			topLevelClass.addMethod(method);

			method = new Method();
			method.setVisibility(JavaVisibility.PUBLIC);
			method.setName(JavaBeansUtil.getSetterMethodName(property));
			method.addParameter(new Parameter(fqjt, property));
			commentGenerator.addSetterComment(method, table, cd.getActualColumnName());

			if (trimStrings && cd.isStringColumn()) {
				sb.setLength(0);
				sb.append("this."); //$NON-NLS-1$
				sb.append(property);
				sb.append(" = "); //$NON-NLS-1$
				sb.append(property);
				sb.append(" == null ? null : "); //$NON-NLS-1$
				sb.append(property);
				sb.append(".trim();"); //$NON-NLS-1$
				method.addBodyLine(sb.toString());
			} else {
				sb.setLength(0);
				sb.append("this."); //$NON-NLS-1$
				sb.append(property);
				sb.append(" = "); //$NON-NLS-1$
				sb.append(property);
				sb.append(';');
				method.addBodyLine(sb.toString());
			}

			topLevelClass.addMethod(method);
		}
	}

	/**
	 * Calculates the package for generated domain objects.
	 * 
	 * @param table
	 *            the current table
	 * @return the calculated package
	 */
	protected String getJavaModelPackage(FullyQualifiedTable table) {
		String key = "getJavaModelPackage"; //$NON-NLS-1$
		String s;

		Map map = getTableValueMap(table);
		s = (String) map.get(key);
		if (s == null) {
			StringBuffer sb = new StringBuffer(targetPackage);
			if ("true".equalsIgnoreCase(properties.getProperty(PropertyRegistry.ANY_ENABLE_SUB_PACKAGES))) { //$NON-NLS-1$
				sb.append(table.getSubPackage());
			}

			s = sb.toString();
			map.put(key, s);
		}

		return s;
	}

	protected TopLevelClass getPrimaryKey(IntrospectedTable introspectedTable) {

		if (!introspectedTable.getRules().generatePrimaryKeyClass()) {
			return null;
		}

		FullyQualifiedTable table = introspectedTable.getTable();
		FullyQualifiedJavaType type = getPrimaryKeyType(table);
		TopLevelClass answer = new TopLevelClass(type);
		answer.setVisibility(JavaVisibility.PUBLIC);
		abatorContext.getCommentGenerator().addJavaFileComment(answer);

		String rootClass = getRootClass(introspectedTable);
		if (rootClass != null) {
			answer.setSuperClass(new FullyQualifiedJavaType(rootClass));
			answer.addImportedType(answer.getSuperClass());
		}

		generateClassParts(table, introspectedTable.getPrimaryKeyColumns(), answer, getRootClass(introspectedTable));

		return answer;
	}

	protected TopLevelClass getBaseRecord(IntrospectedTable introspectedTable) {

		if (!introspectedTable.getRules().generateBaseRecordClass()) {
			return null;
		}

		FullyQualifiedTable table = introspectedTable.getTable();
		FullyQualifiedJavaType type = getBaseRecordType(table);
		TopLevelClass answer = new TopLevelClass(type);
		answer.setVisibility(JavaVisibility.PUBLIC);
		abatorContext.getCommentGenerator().addJavaFileComment(answer);

		if (introspectedTable.getRules().generatePrimaryKeyClass()) {
			answer.setSuperClass(getPrimaryKeyType(table));
		} else {
			String rootClass = getRootClass(introspectedTable);
			if (rootClass != null) {
				answer.setSuperClass(new FullyQualifiedJavaType(rootClass));
				answer.addImportedType(answer.getSuperClass());
			}
		}

		if (!introspectedTable.getRules().generatePrimaryKeyClass() && introspectedTable.hasPrimaryKeyColumns()) {
			generateClassParts(table, introspectedTable.getPrimaryKeyColumns(), answer, getRootClass(introspectedTable));
		}

		generateClassParts(table, introspectedTable.getBaseColumns(), answer, getRootClass(introspectedTable));

		if (!introspectedTable.getRules().generateRecordWithBLOBsClass() && introspectedTable.hasBLOBColumns()) {
			generateClassParts(table, introspectedTable.getBLOBColumns(), answer, getRootClass(introspectedTable));
		}

		return answer;
	}

	protected FullyQualifiedJavaType getSuperAsertConditionType(FullyQualifiedTable table) {
		FullyQualifiedJavaType fqjp = new FullyQualifiedJavaType("cn.nosir.ibatis.abator.AsertCondition");
		return fqjp;
	}

	protected TopLevelClass getAsertCondition(IntrospectedTable introspectedTable) {
		if (!introspectedTable.getRules().generateBaseRecordClass()) {
			return null;
		}
		FullyQualifiedTable table = introspectedTable.getTable();
		FullyQualifiedJavaType type = getAsertConditionType(table);
		TopLevelClass answer = new TopLevelClass(type);
		answer.setVisibility(JavaVisibility.PUBLIC);
		answer.setSuperClass(getSuperAsertConditionType(table));
		answer.addImportedType(getSuperAsertConditionType(table));
		abatorContext.getCommentGenerator().addJavaFileComment(answer);
		/** 所有字段 */
		Iterator pricols = introspectedTable.getPrimaryKeyColumns();
		Iterator bascols = introspectedTable.getBaseColumns();
		ArrayList columns = new ArrayList();

		FullyQualifiedJavaType fqjt = new FullyQualifiedJavaType("boolean");
		while (pricols.hasNext()) {
			columns.add(pricols.next());
		}
		// base include exclude
		while (bascols.hasNext()) {
			columns.add(bascols.next());
		}

		for (int i = 0; i != columns.size(); i++) {
			ColumnDefinition cd = (ColumnDefinition) columns.get(i);
			// if (propertyExistsInRootClass(cd, rootClass)) {
			// continue;
			// }
			answer.addImportedType(fqjt);
			// add field
			Field field = new Field();
			field.setVisibility(JavaVisibility.PRIVATE);
			field.setType(fqjt);
			field.setName(cd.getJavaProperty());
			answer.addField(field);

			// include method
			StringBuilder property = new StringBuilder();
			property.append("i");
			property.append(JavaBeansUtil.getCamelCaseString(cd.getJavaProperty(), true));

			Method method = new Method();
			method.setVisibility(JavaVisibility.PUBLIC);
			method.setReturnType(type);
			method.setName(property.toString());
			StringBuffer sb = new StringBuffer();
			sb.setLength(0);
			sb.append("this.");
			sb.append(field.getName());
			sb.append(" = true;\n");
			sb.append("\t\treturn this;");
			method.addBodyLine(sb.toString());
			answer.addMethod(method);

			// exclude method
			property.setCharAt(0, 'e');

			method = new Method();
			method.setVisibility(JavaVisibility.PUBLIC);
			method.setReturnType(type);
			method.setName(property.toString());
			sb = new StringBuffer();
			sb.setLength(0);
			sb.append("this.");
			sb.append(field.getName());
			sb.append(" = false;\n");
			sb.append("\t\treturn this;");
			method.addBodyLine(sb.toString());
			answer.addMethod(method);

			// Getter
			method = new Method();
			method.setVisibility(JavaVisibility.PUBLIC);
			method.setReturnType(fqjt);
			method.setName(JavaBeansUtil.getGetterMethodName(cd.getJavaProperty(), method.getReturnType()));
			sb.setLength(0);
			sb.append("return this.");
			sb.append(cd.getJavaProperty());
			sb.append(";");
			method.addBodyLine(sb.toString());
			answer.addMethod(method);

		}

		return answer;
	}

	protected TopLevelClass getRecordWithBLOBs(IntrospectedTable introspectedTable) {

		if (!introspectedTable.getRules().generateRecordWithBLOBsClass()) {
			return null;
		}

		FullyQualifiedTable table = introspectedTable.getTable();
		FullyQualifiedJavaType type = getRecordWithBLOBsType(table);
		TopLevelClass answer = new TopLevelClass(type);
		answer.setVisibility(JavaVisibility.PUBLIC);
		abatorContext.getCommentGenerator().addJavaFileComment(answer);

		if (introspectedTable.getRules().generateBaseRecordClass()) {
			answer.setSuperClass(getBaseRecordType(table));
		} else {
			answer.setSuperClass(getPrimaryKeyType(table));
		}

		generateClassParts(table, introspectedTable.getBLOBColumns(), answer, getRootClass(introspectedTable));

		return answer;
	}

	public void setTargetProject(String targetProject) {
		this.targetProject = targetProject;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.ibatis.abator.api.JavaModelGenerator#getExampleType(org.apache
	 * .ibatis.abator.config.FullyQualifiedTable)
	 */
	public FullyQualifiedJavaType getExampleType(FullyQualifiedTable table) {
		String key = "getExampleType"; //$NON-NLS-1$

		Map map = getTableValueMap(table);
		FullyQualifiedJavaType fqjt = (FullyQualifiedJavaType) map.get(key);
		if (fqjt == null) {
			StringBuffer sb = new StringBuffer();
			sb.append(getJavaModelPackage(table));
			sb.append('.');
			sb.append(table.getDomainObjectName());
			sb.append("Example"); //$NON-NLS-1$

			fqjt = new FullyQualifiedJavaType(sb.toString());
			map.put(key, fqjt);
		}

		return fqjt;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.ibatis.abator.api.JavaModelGenerator#getGeneratedJavaFiles
	 * (org.apache.ibatis.abator.api.IntrospectedTable,
	 * org.apache.ibatis.abator.api.ProgressCallback)
	 */
	public List getGeneratedJavaFiles(IntrospectedTable introspectedTable, ProgressCallback callback) {
		List list = new ArrayList();
		// Generating Example class for table
		callback.startSubTask(Messages.getString("Progress.6", //$NON-NLS-1$
				introspectedTable.getTable().toString()));
		TopLevelClass tlc = getExample(introspectedTable);
		if (tlc != null) {
			afterExampleGenerationHook(introspectedTable, tlc);
			GeneratedJavaFile gjf = new GeneratedJavaFile(tlc, targetProject);
			list.add(gjf);
		}
		/** add by lixiaodan 2013-02-28 add */
		// callback.startSubTask(Messages.getString(
		//        		"Progress.6", //$NON-NLS-1$
		// introspectedTable.getTable().toString()));
		tlc = getAsertCondition(introspectedTable);
		if (tlc != null) {
			afterAsertConditionGenerationHook(introspectedTable, tlc);
			GeneratedJavaFile gjf = new GeneratedJavaFile(tlc, targetProject);
			list.add(gjf);
		}
		/** add by lixiaodan end */

		// Generating Primary Key class for table
		callback.startSubTask(Messages.getString("Progress.7", //$NON-NLS-1$
				introspectedTable.getTable().toString()));
		tlc = getPrimaryKey(introspectedTable);
		if (tlc != null) {
			afterPrimaryKeyGenerationHook(introspectedTable, tlc);
			GeneratedJavaFile gjf = new GeneratedJavaFile(tlc, targetProject);
			list.add(gjf);
		}
		// Record class for table
		callback.startSubTask(Messages.getString("Progress.8", //$NON-NLS-1$
				introspectedTable.getTable().toString()));
		tlc = getBaseRecord(introspectedTable);
		if (tlc != null) {
			afterBaseRecordGenerationHook(introspectedTable, tlc);
			GeneratedJavaFile gjf = new GeneratedJavaFile(tlc, targetProject);
			list.add(gjf);
		}
		// Record class(with BLOBs) for table
		callback.startSubTask(Messages.getString("Progress.9", //$NON-NLS-1$
				introspectedTable.getTable().toString()));
		tlc = getRecordWithBLOBs(introspectedTable);
		if (tlc != null) {
			afterRecordWithBLOBsGenerationHook(introspectedTable, tlc);
			GeneratedJavaFile gjf = new GeneratedJavaFile(tlc, targetProject);
			list.add(gjf);
		}

		return list;
	}

	/**
	 * Override this method to provide any extra customization needed in the
	 * generated example class
	 * 
	 * @param introspectedTable
	 * @param topLevelClass
	 */
	protected void afterExampleGenerationHook(IntrospectedTable introspectedTable, TopLevelClass topLevelClass) {
		return;
	}

	/**
	 * Override this method to provide any extra customization needed in the
	 * generated primary key class
	 * 
	 * @param introspectedTable
	 * @param topLevelClass
	 */
	protected void afterPrimaryKeyGenerationHook(IntrospectedTable introspectedTable, TopLevelClass topLevelClass) {
		return;
	}

	/**
	 * Override this method to provide any extra customization needed in the
	 * generated base record class
	 * 
	 * @param introspectedTable
	 * @param topLevelClass
	 */
	protected void afterBaseRecordGenerationHook(IntrospectedTable introspectedTable, TopLevelClass topLevelClass) {
		return;
	}

	/**
	 * Override this method to provide any extra customization needed in the
	 * generated base record class
	 * 
	 * @param introspectedTable
	 * @param topLevelClass
	 */
	protected void afterAsertConditionGenerationHook(IntrospectedTable introspectedTable, TopLevelClass topLevelClass) {
		return;
	}

	/**
	 * Override this method to provide any extra customization needed in the
	 * generated base record class
	 * 
	 * @param introspectedTable
	 * @param topLevelClass
	 */
	protected void afterRecordWithBLOBsGenerationHook(IntrospectedTable introspectedTable, TopLevelClass topLevelClass) {
		return;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.ibatis.abator.api.JavaModelGenerator#getPrimaryKeyType(org
	 * .apache.ibatis.abator.config.FullyQualifiedTable)
	 */
	public FullyQualifiedJavaType getPrimaryKeyType(FullyQualifiedTable table) {
		String key = "getPrimaryKeyType"; //$NON-NLS-1$

		Map map = getTableValueMap(table);
		FullyQualifiedJavaType fqjt = (FullyQualifiedJavaType) map.get(key);
		if (fqjt == null) {
			StringBuffer sb = new StringBuffer();
			sb.append(getJavaModelPackage(table));
			sb.append('.');
			sb.append(table.getDomainObjectName());
			sb.append("Key"); //$NON-NLS-1$

			fqjt = new FullyQualifiedJavaType(sb.toString());
			map.put(key, fqjt);
		}

		return fqjt;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.ibatis.abator.api.JavaModelGenerator#getRecordType(org.apache
	 * .ibatis.abator.config.FullyQualifiedTable)
	 */
	public FullyQualifiedJavaType getBaseRecordType(FullyQualifiedTable table) {
		String key = "getRecordType"; //$NON-NLS-1$

		Map map = getTableValueMap(table);
		FullyQualifiedJavaType fqjt = (FullyQualifiedJavaType) map.get(key);
		if (fqjt == null) {
			StringBuffer sb = new StringBuffer();
			sb.append(getJavaModelPackage(table));
			sb.append('.');
			sb.append(table.getDomainObjectName());

			fqjt = new FullyQualifiedJavaType(sb.toString());
			map.put(key, fqjt);
		}

		return fqjt;
	}

	public FullyQualifiedJavaType getAsertConditionType(FullyQualifiedTable table) {
		String key = "getAsertConditionType"; //$NON-NLS-1$

		Map map = getTableValueMap(table);
		FullyQualifiedJavaType fqjt = (FullyQualifiedJavaType) map.get(key);
		if (fqjt == null) {
			StringBuffer sb = new StringBuffer();
			sb.append(getJavaModelPackage(table));
			sb.append('.');
			sb.append(table.getDomainObjectName());
			sb.append("AC");

			fqjt = new FullyQualifiedJavaType(sb.toString());
			map.put(key, fqjt);
		}

		return fqjt;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.ibatis.abator.api.JavaModelGenerator#getRecordWithBLOBsType
	 * (org.apache.ibatis.abator.config.FullyQualifiedTable)
	 */
	public FullyQualifiedJavaType getRecordWithBLOBsType(FullyQualifiedTable table) {
		String key = "getRecordWithBLOBsType"; //$NON-NLS-1$

		Map map = getTableValueMap(table);
		FullyQualifiedJavaType fqjt = (FullyQualifiedJavaType) map.get(key);
		if (fqjt == null) {
			StringBuffer sb = new StringBuffer();
			sb.append(getJavaModelPackage(table));
			sb.append('.');
			sb.append(table.getDomainObjectName());
			sb.append("WithBLOBs"); //$NON-NLS-1$

			fqjt = new FullyQualifiedJavaType(sb.toString());
			map.put(key, fqjt);
		}

		return fqjt;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * org.apache.ibatis.abator.api.JavaModelGenerator#setWarnings(java.util
	 * .List)
	 */
	public void setWarnings(List warnings) {
		this.warnings = warnings;
	}

	protected Method getSetNullMethod(ColumnDefinition cd) {
		return getNoValueMethod(cd, "IsNull", "is null"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSetNotNullMethod(ColumnDefinition cd) {
		return getNoValueMethod(cd, "IsNotNull", "is not null"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSetEqualMethod(ColumnDefinition cd) {
		return getSingleValueMethod(cd, "EqualTo", "="); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSetNotEqualMethod(ColumnDefinition cd) {
		return getSingleValueMethod(cd, "NotEqualTo", "<>"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSetGreaterThanMethod(ColumnDefinition cd) {
		return getSingleValueMethod(cd, "GreaterThan", ">"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSetGreaterThenOrEqualMethod(ColumnDefinition cd) {
		return getSingleValueMethod(cd, "GreaterThanOrEqualTo", ">="); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSetLessThanMethod(ColumnDefinition cd) {
		return getSingleValueMethod(cd, "LessThan", "<"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSetLessThanOrEqualMethod(ColumnDefinition cd) {
		return getSingleValueMethod(cd, "LessThanOrEqualTo", "<="); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSetLikeMethod(ColumnDefinition cd) {
		return getSingleValueMethod(cd, "Like", "like"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSetNotLikeMethod(ColumnDefinition cd) {
		return getSingleValueMethod(cd, "NotLike", "not like"); //$NON-NLS-1$ //$NON-NLS-2$
	}

	protected Method getSingleValueMethod(ColumnDefinition cd, String nameFragment, String operator) {
		Method method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.addParameter(new Parameter(cd.getResolvedJavaType().getFullyQualifiedJavaType(), "value")); //$NON-NLS-1$
		StringBuffer sb = new StringBuffer();
		sb.append(cd.getJavaProperty());
		sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
		sb.insert(0, "and"); //$NON-NLS-1$
		sb.append(nameFragment);
		method.setName(sb.toString());
		method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
		sb.setLength(0);

		if (cd.isJDBCDateColumn()) {
			sb.append("addCriterionForJDBCDate(\""); //$NON-NLS-1$
		} else if (cd.isJDBCTimeColumn()) {
			sb.append("addCriterionForJDBCTime(\""); //$NON-NLS-1$
		} else if (StringUtility.stringHasValue(cd.getTypeHandler())) {
			sb.append("add"); //$NON-NLS-1$
			sb.append(cd.getJavaProperty());
			sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
			sb.append("Criterion(\""); //$NON-NLS-1$
		} else {
			sb.append("addCriterion(\""); //$NON-NLS-1$
		}

		sb.append(cd.getAliasedActualColumnName());
		sb.append(' ');
		sb.append(operator);
		sb.append("\", "); //$NON-NLS-1$

		if (cd.getResolvedJavaType().getFullyQualifiedJavaType().isPrimitive()) {
			sb.append("new "); //$NON-NLS-1$
			sb.append(cd.getResolvedJavaType().getFullyQualifiedJavaType().getPrimitiveTypeWrapper().getShortName());
			sb.append("(value)"); //$NON-NLS-1$
		} else {
			sb.append("value"); //$NON-NLS-1$
		}

		sb.append(", \""); //$NON-NLS-1$
		sb.append(cd.getJavaProperty());
		sb.append("\");"); //$NON-NLS-1$
		method.addBodyLine(sb.toString());
		method.addBodyLine("return this;"); //$NON-NLS-1$

		return method;
	}

	protected Method getNoValueMethod(ColumnDefinition cd, String nameFragment, String operator) {
		Method method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		if (!(this instanceof JavaModelGeneratorJava5Impl)) {
			if (abatorContext.getSuppressTypeWarnings()) {
				method.addSuppressTypeWarningsAnnotation();
			}
		}
		StringBuffer sb = new StringBuffer();
		sb.append(cd.getJavaProperty());
		sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
		sb.insert(0, "and"); //$NON-NLS-1$
		sb.append(nameFragment);
		method.setName(sb.toString());
		method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
		sb.setLength(0);
		sb.append("addCriterion(\""); //$NON-NLS-1$
		sb.append(cd.getAliasedActualColumnName());
		sb.append(' ');
		sb.append(operator);
		sb.append("\");"); //$NON-NLS-1$
		method.addBodyLine(sb.toString());
		method.addBodyLine("return this;"); //$NON-NLS-1$

		return method;
	}

	/**
	 * Generates methods that set between and not between conditions
	 * 
	 * @param cd
	 * @param betweenMethod
	 * @return a generated method for the between or not between method
	 */
	protected Method getSetBetweenOrNotBetweenMethod(ColumnDefinition cd, boolean betweenMethod) {
		Method method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		FullyQualifiedJavaType type = cd.getResolvedJavaType().getFullyQualifiedJavaType();

		method.addParameter(new Parameter(type, "value1")); //$NON-NLS-1$
		method.addParameter(new Parameter(type, "value2")); //$NON-NLS-1$
		StringBuffer sb = new StringBuffer();
		sb.append(cd.getJavaProperty());
		sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
		sb.insert(0, "and"); //$NON-NLS-1$
		if (betweenMethod) {
			sb.append("Between"); //$NON-NLS-1$
		} else {
			sb.append("NotBetween"); //$NON-NLS-1$
		}
		method.setName(sb.toString());
		method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
		sb.setLength(0);

		if (cd.isJDBCDateColumn()) {
			sb.append("addCriterionForJDBCDate(\""); //$NON-NLS-1$
		} else if (cd.isJDBCTimeColumn()) {
			sb.append("addCriterionForJDBCTime(\""); //$NON-NLS-1$
		} else if (StringUtility.stringHasValue(cd.getTypeHandler())) {
			sb.append("add"); //$NON-NLS-1$
			sb.append(cd.getJavaProperty());
			sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
			sb.append("Criterion(\""); //$NON-NLS-1$
		} else {
			sb.append("addCriterion(\""); //$NON-NLS-1$
		}

		sb.append(cd.getAliasedActualColumnName());
		if (betweenMethod) {
			sb.append(" between"); //$NON-NLS-1$
		} else {
			sb.append(" not between"); //$NON-NLS-1$
		}
		sb.append("\", "); //$NON-NLS-1$
		if (cd.getResolvedJavaType().getFullyQualifiedJavaType().isPrimitive()) {
			sb.append("new "); //$NON-NLS-1$
			sb.append(cd.getResolvedJavaType().getFullyQualifiedJavaType().getPrimitiveTypeWrapper().getShortName());
			sb.append("(value1), "); //$NON-NLS-1$
			sb.append("new "); //$NON-NLS-1$
			sb.append(cd.getResolvedJavaType().getFullyQualifiedJavaType().getPrimitiveTypeWrapper().getShortName());
			sb.append("(value2)"); //$NON-NLS-1$
		} else {
			sb.append("value1, value2"); //$NON-NLS-1$
		}

		sb.append(", \""); //$NON-NLS-1$
		sb.append(cd.getJavaProperty());
		sb.append("\");"); //$NON-NLS-1$
		method.addBodyLine(sb.toString());
		method.addBodyLine("return this;"); //$NON-NLS-1$

		return method;
	}

	protected TopLevelClass getExample(IntrospectedTable introspectedTable) {
		if (!introspectedTable.getRules().generateExampleClass()) {
			return null;
		}

		CommentGenerator commentGenerator = abatorContext.getCommentGenerator();

		FullyQualifiedTable table = introspectedTable.getTable();
		FullyQualifiedJavaType type = getExampleType(table);
		TopLevelClass topLevelClass = new TopLevelClass(type);
		topLevelClass.setVisibility(JavaVisibility.PUBLIC);
		commentGenerator.addJavaFileComment(topLevelClass);

		// add default constructor
		Method method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setConstructor(true);
		method.setName(type.getShortName());
		method.addBodyLine("oredCriteria = new ArrayList();"); //$NON-NLS-1$
		commentGenerator.addGeneralMethodComment(method, table);
		topLevelClass.addMethod(method);

		// add shallow copy contructor if the update by
		// example methods are enabled - because the parameter
		// class for update by example methods will subclass this class
		AbatorRules rules = introspectedTable.getRules();
		if (rules.generateUpdateByExampleSelective() || rules.generateUpdateByExampleWithBLOBs() || rules.generateUpdateByExampleWithoutBLOBs()) {
			method = new Method();
			method.setVisibility(JavaVisibility.PROTECTED);
			method.setConstructor(true);
			method.setName(type.getShortName());
			method.addParameter(new Parameter(type, "example")); //$NON-NLS-1$
			method.addBodyLine("this.orderByClause = example.orderByClause;"); //$NON-NLS-1$
			method.addBodyLine("this.oredCriteria = example.oredCriteria;"); //$NON-NLS-1$
			commentGenerator.addGeneralMethodComment(method, table);
			topLevelClass.addMethod(method);
		}

		// add field, getter, setter for orderby clause
		Field field = new Field();
		field.setVisibility(JavaVisibility.PROTECTED);
		field.setType(FullyQualifiedJavaType.getStringInstance());
		field.setName("orderByClause"); //$NON-NLS-1$
		commentGenerator.addFieldComment(field, table);
		topLevelClass.addField(field);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setName("setOrderByClause"); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "orderByClause")); //$NON-NLS-1$
		method.addBodyLine("this.orderByClause = orderByClause;"); //$NON-NLS-1$
		commentGenerator.addGeneralMethodComment(method, table);
		topLevelClass.addMethod(method);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(FullyQualifiedJavaType.getStringInstance());
		method.setName("getOrderByClause"); //$NON-NLS-1$
		method.addBodyLine("return orderByClause;"); //$NON-NLS-1$
		commentGenerator.addGeneralMethodComment(method, table);
		topLevelClass.addMethod(method);

		// add field and methods for the list of ored criteria
		field = new Field();
		field.setVisibility(JavaVisibility.PROTECTED);

		FullyQualifiedJavaType fqjt = FullyQualifiedJavaType.getNewListInstance();

		field.setType(fqjt);
		field.setName("oredCriteria"); //$NON-NLS-1$
		commentGenerator.addFieldComment(field, table);
		topLevelClass.addField(field);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(fqjt);
		method.setName("getOredCriteria"); //$NON-NLS-1$
		method.addBodyLine("return oredCriteria;"); //$NON-NLS-1$
		commentGenerator.addGeneralMethodComment(method, table);
		topLevelClass.addMethod(method);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		if (abatorContext.getSuppressTypeWarnings()) {
			method.addSuppressTypeWarningsAnnotation();
		}
		method.setName("or"); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getCriteriaInstance(), "criteria")); //$NON-NLS-1$
		method.addBodyLine("oredCriteria.add(criteria);"); //$NON-NLS-1$

		commentGenerator.addGeneralMethodComment(method, table);
		topLevelClass.addMethod(method);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		if (abatorContext.getSuppressTypeWarnings()) {
			method.addSuppressTypeWarningsAnnotation();
		}
		method.setName("createCriteria"); //$NON-NLS-1$
		method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
		method.addBodyLine("Criteria criteria = createCriteriaInternal();"); //$NON-NLS-1$
		method.addBodyLine("if (oredCriteria.size() == 0) {"); //$NON-NLS-1$
		method.addBodyLine("oredCriteria.add(criteria);"); //$NON-NLS-1$
		method.addBodyLine("}"); //$NON-NLS-1$
		method.addBodyLine("return criteria;"); //$NON-NLS-1$
		commentGenerator.addGeneralMethodComment(method, table);
		topLevelClass.addMethod(method);

		method = new Method();
		method.setVisibility(JavaVisibility.PROTECTED);
		method.setName("createCriteriaInternal"); //$NON-NLS-1$
		method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
		method.addBodyLine("Criteria criteria = new Criteria();"); //$NON-NLS-1$
		method.addBodyLine("return criteria;"); //$NON-NLS-1$
		commentGenerator.addGeneralMethodComment(method, table);
		topLevelClass.addMethod(method);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setName("clear"); //$NON-NLS-1$
		method.addBodyLine("oredCriteria.clear();"); //$NON-NLS-1$
		commentGenerator.addGeneralMethodComment(method, table);
		topLevelClass.addMethod(method);

		// now generate the inner class that holds the AND conditions
		topLevelClass.addInnerClass(getCriteriaInnerClass(topLevelClass, introspectedTable));

		return topLevelClass;
	}

	protected InnerClass getCriteriaInnerClass(TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
		Field field;
		Method method;

		InnerClass answer = new InnerClass(FullyQualifiedJavaType.getCriteriaInstance());

		answer.setVisibility(JavaVisibility.PUBLIC);
		answer.setModifierStatic(true);
		abatorContext.getCommentGenerator().addClassComment(answer, introspectedTable.getTable());

		method = new Method();
		method.setVisibility(JavaVisibility.PROTECTED);
		method.setName("Criteria"); //$NON-NLS-1$
		method.setConstructor(true);
		method.addBodyLine("super();"); //$NON-NLS-1$
		method.addBodyLine("criteriaWithoutValue = new ArrayList();"); //$NON-NLS-1$
		method.addBodyLine("criteriaWithSingleValue = new ArrayList();"); //$NON-NLS-1$
		method.addBodyLine("criteriaWithListValue = new ArrayList();"); //$NON-NLS-1$
		method.addBodyLine("criteriaWithBetweenValue = new ArrayList();"); //$NON-NLS-1$
		answer.addMethod(method);

		List criteriaLists = new ArrayList();
		criteriaLists.add("criteriaWithoutValue"); //$NON-NLS-1$
		criteriaLists.add("criteriaWithSingleValue"); //$NON-NLS-1$
		criteriaLists.add("criteriaWithListValue"); //$NON-NLS-1$
		criteriaLists.add("criteriaWithBetweenValue"); //$NON-NLS-1$

		Iterator iter = introspectedTable.getNonBLOBColumns();
		while (iter.hasNext()) {
			ColumnDefinition cd = (ColumnDefinition) iter.next();

			if (StringUtility.stringHasValue(cd.getTypeHandler())) {
				criteriaLists.addAll(addtypeHandledObjectsAndMethods(cd, method, answer));
			}
		}

		// now generate the isValid method
		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setName("isValid"); //$NON-NLS-1$
		method.setReturnType(FullyQualifiedJavaType.getBooleanPrimitiveInstance());
		iter = criteriaLists.iterator();
		StringBuffer sb = new StringBuffer();
		sb.append("return "); //$NON-NLS-1$
		sb.append(iter.next());
		sb.append(".size() > 0"); //$NON-NLS-1$
		method.addBodyLine(sb.toString());
		while (iter.hasNext()) {
			sb.setLength(0);
			OutputUtilities.javaIndent(sb, 1);
			sb.append("|| "); //$NON-NLS-1$
			sb.append(iter.next());
			sb.append(".size() > 0"); //$NON-NLS-1$
			if (!iter.hasNext()) {
				sb.append(';');
			}
			method.addBodyLine(sb.toString());
		}
		answer.addMethod(method);

		// now we need to generate the methods that will be used in the SqlMap
		// to generate the dynamic where clause
		topLevelClass.addImportedType(FullyQualifiedJavaType.getNewMapInstance());
		topLevelClass.addImportedType(FullyQualifiedJavaType.getNewListInstance());
		topLevelClass.addImportedType(FullyQualifiedJavaType.getNewHashMapInstance());
		topLevelClass.addImportedType(FullyQualifiedJavaType.getNewArrayListInstance());

		field = new Field();
		field.setVisibility(JavaVisibility.PROTECTED);
		FullyQualifiedJavaType listOfStrings = FullyQualifiedJavaType.getNewListInstance();
		field.setType(listOfStrings);
		field.setName("criteriaWithoutValue"); //$NON-NLS-1$
		answer.addField(field);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(field.getType());
		method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
		method.addBodyLine("return criteriaWithoutValue;"); //$NON-NLS-1$
		answer.addMethod(method);

		FullyQualifiedJavaType listOfMaps = FullyQualifiedJavaType.getNewListInstance();

		field = new Field();
		field.setVisibility(JavaVisibility.PROTECTED);
		field.setType(listOfMaps);
		field.setName("criteriaWithSingleValue"); //$NON-NLS-1$
		answer.addField(field);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(field.getType());
		method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
		method.addBodyLine("return criteriaWithSingleValue;"); //$NON-NLS-1$
		answer.addMethod(method);

		field = new Field();
		field.setVisibility(JavaVisibility.PROTECTED);
		field.setType(listOfMaps);
		field.setName("criteriaWithListValue"); //$NON-NLS-1$
		answer.addField(field);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(field.getType());
		method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
		method.addBodyLine("return criteriaWithListValue;"); //$NON-NLS-1$
		answer.addMethod(method);

		field = new Field();
		field.setVisibility(JavaVisibility.PROTECTED);
		field.setType(listOfMaps);
		field.setName("criteriaWithBetweenValue"); //$NON-NLS-1$
		answer.addField(field);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(field.getType());
		method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
		method.addBodyLine("return criteriaWithBetweenValue;"); //$NON-NLS-1$
		answer.addMethod(method);

		// now add the methods for simplifying the individual field set methods
		method = new Method();
		method.setVisibility(JavaVisibility.PROTECTED);
		if (abatorContext.getSuppressTypeWarnings()) {
			method.addSuppressTypeWarningsAnnotation();
		}
		method.setName("addCriterion"); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
		method.addBodyLine("if (condition == null) {"); //$NON-NLS-1$
		method.addBodyLine("throw new RuntimeException(\"Value for condition cannot be null\");"); //$NON-NLS-1$
		method.addBodyLine("}"); //$NON-NLS-1$
		method.addBodyLine("criteriaWithoutValue.add(condition);"); //$NON-NLS-1$
		answer.addMethod(method);

		method = new Method();
		method.setVisibility(JavaVisibility.PROTECTED);
		if (abatorContext.getSuppressTypeWarnings()) {
			method.addSuppressTypeWarningsAnnotation();
		}
		method.setName("addCriterion"); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getObjectInstance(), "value")); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
		method.addBodyLine("if (value == null) {"); //$NON-NLS-1$
		method.addBodyLine("throw new RuntimeException(\"Value for \" + property + \" cannot be null\");"); //$NON-NLS-1$
		method.addBodyLine("}"); //$NON-NLS-1$
		method.addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"value\", value);"); //$NON-NLS-1$
		method.addBodyLine("criteriaWithSingleValue.add(map);"); //$NON-NLS-1$
		answer.addMethod(method);

		FullyQualifiedJavaType listOfObjects = FullyQualifiedJavaType.getNewListInstance();

		method = new Method();
		method.setVisibility(JavaVisibility.PROTECTED);
		if (abatorContext.getSuppressTypeWarnings()) {
			method.addSuppressTypeWarningsAnnotation();
		}
		method.setName("addCriterion"); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
		method.addParameter(new Parameter(listOfObjects, "values")); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
		method.addBodyLine("if (values == null || values.size() == 0) {"); //$NON-NLS-1$
		method.addBodyLine("throw new RuntimeException(\"Value list for \" + property + \" cannot be null or empty\");"); //$NON-NLS-1$
		method.addBodyLine("}"); //$NON-NLS-1$
		method.addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"values\", values);"); //$NON-NLS-1$
		method.addBodyLine("criteriaWithListValue.add(map);"); //$NON-NLS-1$
		answer.addMethod(method);

		method = new Method();
		method.setVisibility(JavaVisibility.PROTECTED);
		if (abatorContext.getSuppressTypeWarnings()) {
			method.addSuppressTypeWarningsAnnotation();
		}
		method.setName("addCriterion"); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getObjectInstance(), "value1")); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getObjectInstance(), "value2")); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
		method.addBodyLine("if (value1 == null || value2 == null) {"); //$NON-NLS-1$
		method.addBodyLine("throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");"); //$NON-NLS-1$
		method.addBodyLine("}"); //$NON-NLS-1$
		method.addBodyLine("List list = new ArrayList();"); //$NON-NLS-1$
		method.addBodyLine("list.add(value1);"); //$NON-NLS-1$
		method.addBodyLine("list.add(value2);"); //$NON-NLS-1$
		method.addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"values\", list);"); //$NON-NLS-1$
		method.addBodyLine("criteriaWithBetweenValue.add(map);"); //$NON-NLS-1$
		answer.addMethod(method);

		FullyQualifiedJavaType listOfDates = FullyQualifiedJavaType.getNewListInstance();

		if (introspectedTable.hasJDBCDateColumns()) {
			topLevelClass.addImportedType(FullyQualifiedJavaType.getDateInstance());
			topLevelClass.addImportedType(FullyQualifiedJavaType.getNewIteratorInstance());
			method = new Method();
			method.setVisibility(JavaVisibility.PROTECTED);
			method.setName("addCriterionForJDBCDate"); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getDateInstance(), "value")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
			method.addBodyLine("addCriterion(condition, new java.sql.Date(value.getTime()), property);"); //$NON-NLS-1$
			answer.addMethod(method);

			method = new Method();
			method.setVisibility(JavaVisibility.PROTECTED);
			if (abatorContext.getSuppressTypeWarnings()) {
				method.addSuppressTypeWarningsAnnotation();
			}
			method.setName("addCriterionForJDBCDate"); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
			method.addParameter(new Parameter(listOfDates, "values")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
			method.addBodyLine("if (values == null || values.size() == 0) {"); //$NON-NLS-1$
			method.addBodyLine("throw new RuntimeException(\"Value list for \" + property + \" cannot be null or empty\");"); //$NON-NLS-1$
			method.addBodyLine("}"); //$NON-NLS-1$
			method.addBodyLine("List dateList = new ArrayList();"); //$NON-NLS-1$
			method.addBodyLine("Iterator iter = values.iterator();"); //$NON-NLS-1$
			method.addBodyLine("while (iter.hasNext()) {"); //$NON-NLS-1$
			method.addBodyLine("dateList.add(new java.sql.Date(((Date)iter.next()).getTime()));"); //$NON-NLS-1$
			method.addBodyLine("}"); //$NON-NLS-1$
			method.addBodyLine("addCriterion(condition, dateList, property);"); //$NON-NLS-1$
			answer.addMethod(method);

			method = new Method();
			method.setVisibility(JavaVisibility.PROTECTED);
			method.setName("addCriterionForJDBCDate"); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getDateInstance(), "value1")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getDateInstance(), "value2")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
			method.addBodyLine("if (value1 == null || value2 == null) {"); //$NON-NLS-1$
			method.addBodyLine("throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");"); //$NON-NLS-1$
			method.addBodyLine("}"); //$NON-NLS-1$
			method.addBodyLine("addCriterion(condition, new java.sql.Date(value1.getTime()), new java.sql.Date(value2.getTime()), property);"); //$NON-NLS-1$
			answer.addMethod(method);
		}

		if (introspectedTable.hasJDBCTimeColumns()) {
			topLevelClass.addImportedType(FullyQualifiedJavaType.getDateInstance());
			topLevelClass.addImportedType(FullyQualifiedJavaType.getNewIteratorInstance());
			method = new Method();
			method.setVisibility(JavaVisibility.PROTECTED);
			method.setName("addCriterionForJDBCTime"); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getDateInstance(), "value")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
			method.addBodyLine("addCriterion(condition, new java.sql.Time(value.getTime()), property);"); //$NON-NLS-1$
			answer.addMethod(method);

			method = new Method();
			method.setVisibility(JavaVisibility.PROTECTED);
			if (abatorContext.getSuppressTypeWarnings()) {
				method.addSuppressTypeWarningsAnnotation();
			}
			method.setName("addCriterionForJDBCTime"); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
			method.addParameter(new Parameter(listOfDates, "values")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
			method.addBodyLine("if (values == null || values.size() == 0) {"); //$NON-NLS-1$
			method.addBodyLine("throw new RuntimeException(\"Value list for \" + property + \" cannot be null or empty\");"); //$NON-NLS-1$
			method.addBodyLine("}"); //$NON-NLS-1$
			method.addBodyLine("List dateList = new ArrayList();"); //$NON-NLS-1$
			method.addBodyLine("Iterator iter = values.iterator();"); //$NON-NLS-1$
			method.addBodyLine("while (iter.hasNext()) {"); //$NON-NLS-1$
			method.addBodyLine("dateList.add(new java.sql.Time(((Date)iter.next()).getTime()));"); //$NON-NLS-1$
			method.addBodyLine("}"); //$NON-NLS-1$
			method.addBodyLine("addCriterion(condition, dateList, property);"); //$NON-NLS-1$
			answer.addMethod(method);

			method = new Method();
			method.setVisibility(JavaVisibility.PROTECTED);
			method.setName("addCriterionForJDBCTime"); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getDateInstance(), "value1")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getDateInstance(), "value2")); //$NON-NLS-1$
			method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
			method.addBodyLine("if (value1 == null || value2 == null) {"); //$NON-NLS-1$
			method.addBodyLine("throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");"); //$NON-NLS-1$
			method.addBodyLine("}"); //$NON-NLS-1$
			method.addBodyLine("addCriterion(condition, new java.sql.Time(value1.getTime()), new java.sql.Time(value2.getTime()), property);"); //$NON-NLS-1$
			answer.addMethod(method);
		}

		iter = introspectedTable.getNonBLOBColumns();
		while (iter.hasNext()) {
			ColumnDefinition cd = (ColumnDefinition) iter.next();

			topLevelClass.addImportedType(cd.getResolvedJavaType().getFullyQualifiedJavaType());

			// here we need to add the individual methods for setting the
			// conditions for a field
			answer.addMethod(getSetNullMethod(cd));
			answer.addMethod(getSetNotNullMethod(cd));
			answer.addMethod(getSetEqualMethod(cd));
			answer.addMethod(getSetNotEqualMethod(cd));
			answer.addMethod(getSetGreaterThanMethod(cd));
			answer.addMethod(getSetGreaterThenOrEqualMethod(cd));
			answer.addMethod(getSetLessThanMethod(cd));
			answer.addMethod(getSetLessThanOrEqualMethod(cd));

			if (cd.isJdbcCharacterColumn()) {
				answer.addMethod(getSetLikeMethod(cd));
				answer.addMethod(getSetNotLikeMethod(cd));
			}

			answer.addMethod(getSetInOrNotInMethod(cd, true));
			answer.addMethod(getSetInOrNotInMethod(cd, false));
			answer.addMethod(getSetBetweenOrNotBetweenMethod(cd, true));
			answer.addMethod(getSetBetweenOrNotBetweenMethod(cd, false));
		}

		return answer;
	}

	/**
	 * 
	 * @param cd
	 * @param inMethod
	 *            if true generates an "in" method, else generates a "not in"
	 *            method
	 * @return a generated method for the in or not in method
	 */
	protected Method getSetInOrNotInMethod(ColumnDefinition cd, boolean inMethod) {
		Method method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		FullyQualifiedJavaType type = FullyQualifiedJavaType.getNewListInstance();
		method.addParameter(new Parameter(type, "values")); //$NON-NLS-1$
		StringBuffer sb = new StringBuffer();
		sb.append(cd.getJavaProperty());
		sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
		sb.insert(0, "and"); //$NON-NLS-1$
		if (inMethod) {
			sb.append("In"); //$NON-NLS-1$
		} else {
			sb.append("NotIn"); //$NON-NLS-1$
		}
		method.setName(sb.toString());
		method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
		sb.setLength(0);

		if (cd.isJDBCDateColumn()) {
			sb.append("addCriterionForJDBCDate(\""); //$NON-NLS-1$
		} else if (cd.isJDBCTimeColumn()) {
			sb.append("addCriterionForJDBCTime(\""); //$NON-NLS-1$
		} else if (StringUtility.stringHasValue(cd.getTypeHandler())) {
			sb.append("add"); //$NON-NLS-1$
			sb.append(cd.getJavaProperty());
			sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
			sb.append("Criterion(\""); //$NON-NLS-1$
		} else {
			sb.append("addCriterion(\""); //$NON-NLS-1$
		}

		sb.append(cd.getAliasedActualColumnName());
		if (inMethod) {
			sb.append(" in"); //$NON-NLS-1$
		} else {
			sb.append(" not in"); //$NON-NLS-1$
		}
		sb.append("\", values, \""); //$NON-NLS-1$
		sb.append(cd.getJavaProperty());
		sb.append("\");"); //$NON-NLS-1$
		method.addBodyLine(sb.toString());
		method.addBodyLine("return this;"); //$NON-NLS-1$

		return method;
	}

	/**
	 * This method adds all the extra methods and fields required to support a
	 * user defined type handler on some column.
	 * 
	 * @param cd
	 * @param constructor
	 * @param innerClass
	 * @return a list of the names of all Lists added to the class by this
	 *         method
	 */
	private List addtypeHandledObjectsAndMethods(ColumnDefinition cd, Method constructor, InnerClass innerClass) {
		List answer = new ArrayList();
		StringBuffer sb = new StringBuffer();

		// add new private fields and public accessors in the class
		FullyQualifiedJavaType listOfMaps = FullyQualifiedJavaType.getNewListInstance();

		sb.setLength(0);
		sb.append(cd.getJavaProperty());
		sb.append("CriteriaWithSingleValue"); //$NON-NLS-1$
		answer.add(sb.toString());

		Field field = new Field();
		field.setVisibility(JavaVisibility.PROTECTED);
		field.setType(listOfMaps);
		field.setName(sb.toString());
		innerClass.addField(field);

		Method method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(field.getType());
		method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
		sb.insert(0, "return "); //$NON-NLS-1$
		sb.append(';');
		method.addBodyLine(sb.toString());
		innerClass.addMethod(method);

		sb.setLength(0);
		sb.append(cd.getJavaProperty());
		sb.append("CriteriaWithListValue"); //$NON-NLS-1$
		answer.add(sb.toString());

		field = new Field();
		field.setVisibility(JavaVisibility.PROTECTED);
		field.setType(listOfMaps);
		field.setName(sb.toString());
		innerClass.addField(field);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(field.getType());
		method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
		sb.insert(0, "return "); //$NON-NLS-1$
		sb.append(';');
		method.addBodyLine(sb.toString());
		innerClass.addMethod(method);

		sb.setLength(0);
		sb.append(cd.getJavaProperty());
		sb.append("CriteriaWithBetweenValue"); //$NON-NLS-1$
		answer.add(sb.toString());

		field = new Field();
		field.setVisibility(JavaVisibility.PROTECTED);
		field.setType(listOfMaps);
		field.setName(sb.toString());
		innerClass.addField(field);

		method = new Method();
		method.setVisibility(JavaVisibility.PUBLIC);
		method.setReturnType(field.getType());
		method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
		sb.insert(0, "return "); //$NON-NLS-1$
		sb.append(';');
		method.addBodyLine(sb.toString());
		innerClass.addMethod(method);

		// add constructor initialization
		sb.setLength(0);
		sb.append(cd.getJavaProperty());
		sb.append("CriteriaWithSingleValue = new ArrayList();"); //$NON-NLS-1$;
		constructor.addBodyLine(sb.toString());

		sb.setLength(0);
		sb.append(cd.getJavaProperty());
		sb.append("CriteriaWithListValue = new ArrayList();"); //$NON-NLS-1$
		constructor.addBodyLine(sb.toString());

		sb.setLength(0);
		sb.append(cd.getJavaProperty());
		sb.append("CriteriaWithBetweenValue = new ArrayList();"); //$NON-NLS-1$
		constructor.addBodyLine(sb.toString());

		// now add the methods for simplifying the individual field set methods
		method = new Method();
		method.setVisibility(JavaVisibility.PROTECTED);
		sb.setLength(0);
		sb.append("add"); //$NON-NLS-1$
		sb.append(cd.getJavaProperty());
		sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
		sb.append("Criterion"); //$NON-NLS-1$

		method.setName(sb.toString());
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
		method.addParameter(new Parameter(cd.getResolvedJavaType().getFullyQualifiedJavaType(), "value")); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
		method.addBodyLine("if (value == null) {"); //$NON-NLS-1$
		method.addBodyLine("throw new RuntimeException(\"Value for \" + property + \" cannot be null\");"); //$NON-NLS-1$
		method.addBodyLine("}"); //$NON-NLS-1$
		method.addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"value\", value);"); //$NON-NLS-1$

		sb.setLength(0);
		sb.append(cd.getJavaProperty());
		sb.append("CriteriaWithSingleValue.add(map);"); //$NON-NLS-1$
		method.addBodyLine(sb.toString());
		innerClass.addMethod(method);

		FullyQualifiedJavaType listOfObjects = FullyQualifiedJavaType.getNewListInstance();

		sb.setLength(0);
		sb.append("add"); //$NON-NLS-1$
		sb.append(cd.getJavaProperty());
		sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
		sb.append("Criterion"); //$NON-NLS-1$

		method = new Method();
		method.setVisibility(JavaVisibility.PROTECTED);
		method.setName(sb.toString());
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
		method.addParameter(new Parameter(listOfObjects, "values")); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
		method.addBodyLine("if (values == null || values.size() == 0) {"); //$NON-NLS-1$
		method.addBodyLine("throw new RuntimeException(\"Value list for \" + property + \" cannot be null or empty\");"); //$NON-NLS-1$
		method.addBodyLine("}"); //$NON-NLS-1$
		method.addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"values\", values);"); //$NON-NLS-1$

		sb.setLength(0);
		sb.append(cd.getJavaProperty());
		sb.append("CriteriaWithListValue.add(map);"); //$NON-NLS-1$
		method.addBodyLine(sb.toString());
		innerClass.addMethod(method);

		sb.setLength(0);
		sb.append("add"); //$NON-NLS-1$
		sb.append(cd.getJavaProperty());
		sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
		sb.append("Criterion"); //$NON-NLS-1$

		method = new Method();
		method.setVisibility(JavaVisibility.PROTECTED);
		method.setName(sb.toString());
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "condition")); //$NON-NLS-1$
		method.addParameter(new Parameter(cd.getResolvedJavaType().getFullyQualifiedJavaType(), "value1")); //$NON-NLS-1$
		method.addParameter(new Parameter(cd.getResolvedJavaType().getFullyQualifiedJavaType(), "value2")); //$NON-NLS-1$
		method.addParameter(new Parameter(FullyQualifiedJavaType.getStringInstance(), "property")); //$NON-NLS-1$
		method.addBodyLine("if (value1 == null || value2 == null) {"); //$NON-NLS-1$
		method.addBodyLine("throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");"); //$NON-NLS-1$
		method.addBodyLine("}"); //$NON-NLS-1$
		method.addBodyLine("List list = new ArrayList();"); //$NON-NLS-1$
		method.addBodyLine("list.add(value1);"); //$NON-NLS-1$
		method.addBodyLine("list.add(value2);"); //$NON-NLS-1$
		method.addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
		method.addBodyLine("map.put(\"values\", list);"); //$NON-NLS-1$

		sb.setLength(0);
		sb.append(cd.getJavaProperty());
		sb.append("CriteriaWithBetweenValue.add(map);"); //$NON-NLS-1$
		method.addBodyLine(sb.toString());
		innerClass.addMethod(method);

		return answer;
	}

	public void setAbatorContext(AbatorContext abatorContext) {
		this.abatorContext = abatorContext;
	}

	protected String getRootClass(IntrospectedTable introspectedTable) {
		String rootClass = introspectedTable.getTableConfigurationProperty(PropertyRegistry.ANY_ROOT_CLASS);
		if (rootClass == null) {
			rootClass = properties.getProperty(PropertyRegistry.ANY_ROOT_CLASS);
		}

		return rootClass;
	}

	// Ashok's enhancement for checking parent classes...
	protected Class loadRootClass(IntrospectedTable introspectedTable) {
		Class clazz = null;

		String rootClass = getRootClass(introspectedTable);

		if (rootClass != null) {
			try {
				clazz = Class.forName(rootClass);
			} catch (Exception e) {
				warnings.add(Messages.getString("Warning.20", rootClass)); //$NON-NLS-1$
			}
		}

		return clazz;
	}

	protected PropertyDescriptor[] getRootClassPropertyDescriptors(String rootClass) {
		if (rootClass == null) {
			return null;
		}

		if (propertyDescriptorMap.containsKey(rootClass)) {
			return (PropertyDescriptor[]) propertyDescriptorMap.get(rootClass);
		}

		// new class not in map already

		Class clazz = null;
		try {
			clazz = getRootclassloader().loadClass(rootClass);
		} catch (Exception e) {
			propertyDescriptorMap.put(rootClass, null);
			warnings.add(Messages.getString("Warning.20", rootClass)); //$NON-NLS-1$
			return null;
		}

		BeanInfo bi;
		try {
			bi = Introspector.getBeanInfo(clazz);
		} catch (IntrospectionException e) {
			propertyDescriptorMap.put(rootClass, null);
			warnings.add(Messages.getString("Warning.20", rootClass)); //$NON-NLS-1$
			return null;
		}

		PropertyDescriptor[] pd = bi.getPropertyDescriptors();
		propertyDescriptorMap.put(rootClass, pd);
		return pd;
	}

	protected boolean propertyExistsInRootClass(ColumnDefinition columnDefinition, String rootClass) {
		PropertyDescriptor[] rootClassProperties = getRootClassPropertyDescriptors(rootClass);
		if (rootClassProperties == null) {
			return false;
		}

		boolean found = false;
		String propertyName = columnDefinition.getJavaProperty();
		String propertyType = columnDefinition.getResolvedJavaType().getFullyQualifiedJavaType().getFullyQualifiedName();

		// get method names from class and check against this column definition.
		// better yet, have a map of method Names. check against it.
		for (int i = 0; i < rootClassProperties.length; i++) {
			if (rootClassProperties[i].getName().equals(propertyName)) {
				// property is in the rootClass...

				// Is it the proper type?
				if (!rootClassProperties[i].getPropertyType().getName().equals(propertyType)) {
					warnings.add(Messages.getString("Warning.21", propertyName, rootClass, propertyType));
					break;
				}

				// Does it have a getter?
				if (rootClassProperties[i].getReadMethod() == null) {
					warnings.add(Messages.getString("Warning.22", propertyName, rootClass));
					break;
				}

				// Does it have a setter?
				if (rootClassProperties[i].getWriteMethod() == null) {
					warnings.add(Messages.getString("Warning.23", propertyName, rootClass));
					break;
				}

				found = true;
				break;
			}
		}

		return found;
	}

	private ClassLoader getRootclassloader() {
		if (rootClassLoader == null) {
			rootClassLoader = ClassloaderUtility.getCustomClassloader(properties.getProperty(PropertyRegistry.MODEL_GENERATOR_ROOT_CLASSPATH));
		}

		return rootClassLoader;
	}
}
